package cn.itsource.hrm.service.impl;

import cn.itsource.hrm.domain.CourseType;
import cn.itsource.hrm.mapper.CourseTypeMapper;
import cn.itsource.hrm.service.ICourseTypeService;
import cn.itsource.hrm.util.AjaxResult;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 * 课程目录 服务实现类
 * </p>
 *
 * @author lixufeng
 * @since 2021-07-29
 */
@Service
public class CourseTypeServiceImpl extends ServiceImpl<CourseTypeMapper, CourseType> implements ICourseTypeService {

    @Autowired
    private CourseTypeMapper courseTypeMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    // 缓存分类的key
    public static final String COURSE_TYPE_CACHE_KEY = "course_type_cache_key";

    /**
     *  为了解决穿透问题，数据库没有的数据也要在redis中设置一个null
      雪崩和击穿：
     方案1 热点数据永不过期   X
     方案2：双重判断上锁（击穿），分开过期（雪崩）    √
     */
    // 创建锁
    private static ReentrantLock reentrantLock = new ReentrantLock();

    /**
     * 获取树结构
     * @return
     */
    @Override
    @Cacheable(cacheNames = COURSE_TYPE_CACHE_KEY, key = "'all'")
    public AjaxResult treeData() {
        List<CourseType> result = new ArrayList<>();
        Object data = redisTemplate.opsForValue().get(COURSE_TYPE_CACHE_KEY);
        if (data != null){
            result = (List<CourseType>) data;
            System.out.println("从redis获得");
        } else {
            if (reentrantLock.tryLock()){
                try {
                    // 从数据库查询
                    result = loadTree();
                    // 缓存到Redis
                    redisTemplate.opsForValue().set(COURSE_TYPE_CACHE_KEY, result,
                            ThreadLocalRandom.current().nextInt(100, 200), TimeUnit.MINUTES);
                    System.out.println("从数据库获得");
                } finally {
                    // 释放锁
                    reentrantLock.unlock();
                }
            } else {
                // 线程先睡一会儿，防止递归调用造成栈溢出
                try {
                    Thread.sleep(100); // millis毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 重试
                treeData();
            }
        }
        return AjaxResult.me().setResultObj(result);
    }

    /**
     * 数据库中的课程分类被修改，删除，添加，
     * Redis中的课程分类需要清空，或者重置，不然会造成缓存数据和数据库数据不一致问题
     * @param entity
     * @return
     */

    @Override
    public boolean insert(CourseType entity) {
        // 删除原来的缓存
        redisTemplate.delete(COURSE_TYPE_CACHE_KEY);
        return super.insert(entity);
    }

    @Override
    public boolean deleteById(Serializable id) {
        // 删除原来的缓存
        redisTemplate.delete(COURSE_TYPE_CACHE_KEY);
        return super.deleteById(id);
    }

    @Override
    public boolean updateById(CourseType entity) {
        // 删除原来的缓存
        redisTemplate.delete(COURSE_TYPE_CACHE_KEY);
        return super.updateById(entity);
    }

    private List<CourseType> loadTree() {
        // 查出所有数据
        List<CourseType> list = courseTypeMapper.selectList(null);
        // 用hashMap把数据封装起来 key是courseType的id value是courseType对象
        HashMap<Long, CourseType> map = new HashMap<>();
        // lambda表达式 遍历数据都装到map里 建立id和courseType的关系
        list.stream().forEach(courseType ->
            map.put(courseType.getId(),courseType)
        );
        // 封装的result结果的集合
        List<CourseType> result = new ArrayList<>();
        // 遍历
        list.stream().forEach(courseType -> {
            // 找第一层级pid为0
            if (courseType.getPid() == 0) {
                // 把pid为0的作为第一层级添加到result
                result.add(courseType);
            } else {
                // 找下一级 pid等于父级的id
                // 根据map中的父级的id得到父级对象
                CourseType parent = map.get(courseType.getPid());
                // 把courseType添加到子级的属性中
                parent.getChildren().add(courseType);
            }
        });
        return result;
    }
}
